#ifndef __BLOCK_QUEUE_HPP__
#define __BLOCK_QUEUE_HPP__

#include <queue>
#include <pthread.h>

template <class T>
class BlockQueue
{
private:
    bool isFull()
    {
        return _cap == _q.size();
    }

    bool isEmpty()
    {
        return _q.size() == 0;
    }
public:
    BlockQueue(int cap)
    : _cap(cap)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_product_cond, nullptr);
        pthread_cond_init(&_consum_cond, nullptr);
    }

    void push(T& data)
    {
        pthread_mutex_lock(&_mutex);

        /*
            使用while循环检测，防止伪唤醒：
            在阻塞队列满，所有生产线程都在等待唤醒的情况下，如果消费线程采用broadcast广播唤醒的方式
            那么所有的生产线程都会被唤醒，此时所有的线程都会进入竞争锁的阶段
            尽管只有一个生产线程能够竞争到锁，但是其他生产线程已经脱离休眠，只要竞争到锁就能直接继续运行
            因此，如果只用一个if检测，就不能保证后续竞争到锁的线程后续操作时队列不满
            即，需要用while循环判断，确保每一个竞争到锁的线程出去时，队列不满
        */
        while (isFull())    //使用循环，保证代码健壮性
        {
            _product_wait_num++;
            pthread_cond_wait(&_product_cond, &_mutex);
            _product_wait_num--;
        }
        _q.push(data);

        if (_consum_wait_num)
            pthread_cond_signal(&_consum_cond);

        pthread_mutex_unlock(&_mutex);
    }

    void pop(T* val)
    {
        pthread_mutex_lock(&_mutex);

        while (isEmpty())   //使用循环，保证代码健壮性
        {
            _consum_wait_num++;
            pthread_cond_wait(&_consum_cond, &_mutex);
            _consum_wait_num--;
        }
        *val = _q.front();
        _q.pop();

        if (_product_wait_num)
            pthread_cond_signal(&_product_cond);

        pthread_mutex_unlock(&_mutex);
    }

    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_product_cond);
        pthread_cond_destroy(&_consum_cond);
    }

private:
    std::queue<T> _q;
    int _cap;
    int _product_wait_num = 0;  //处于等待状态的生产线程的个数
    int _consum_wait_num = 0;   //处于等待状态的消费线程的个数

    pthread_mutex_t _mutex;     //生产者、消费者共用一个锁
    pthread_cond_t _product_cond;   //供生产者使用的条件变量
    pthread_cond_t _consum_cond;    //供消费者使用的条件变量
};

#endif